﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace RevitAddin
{
    public class XYZ
    {
        public double X { get; set; }
        public double Y { get; set; }
        public double Z { get; set; }

        public static XYZ Zero { get { return new XYZ(0, 0, 0); } }
        public static XYZ BaseX { get { return new XYZ(1, 0, 0); } }
        public static XYZ BaseY { get { return new XYZ(0, 1, 0); } }
        public static XYZ BaseZ { get { return new XYZ(0, 0, 1); } }

        // 静态单位向量定义
        public static XYZ BasisX => new XYZ(1.0, 0.0, 0.0);
        public static XYZ BasisY => new XYZ(0.0, 1.0, 0.0);
        public static XYZ BasisZ => new XYZ(0.0, 0.0, 1.0);

        /// <summary>
        /// 点坐标相加
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public XYZ Add(XYZ source)
        {
            return new XYZ(X + source.X, Y + source.Y, Z + source.Z);
        }

        /// <summary>
        /// 返回两点之间的距离值
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public double DistanceTo(XYZ source)
        {
            return Math.Sqrt(Math.Pow(X - source.X, 2) + Math.Pow(Y - source.Y, 2) + Math.Pow(Z - source.Z, 2));
        }

        /// <summary>
        /// 返回单位向量
        /// </summary>
        /// <returns></returns>
        public XYZ Normalize()
        {
            double length = Math.Sqrt(Math.Pow(X, 2) + Math.Pow(Y, 2) + Math.Pow(Z, 2));
            return new XYZ(X * 1.0 / length, Y * 1.0 / length, Z * 1.0 / length);
        }

        /// <summary>
        /// 返回向量长度倍数的向量
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public XYZ Multiply(double value)
        {
            return new XYZ(X * value, Y * value, Z * value);
        }

        /// <summary>
        /// 返回两个各个向量的夹角
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public double AngleTo(XYZ source)
        {
            return Math.Acos(this.DotProduct(source) / (this.Length() * source.Length()));
        }

        /// <summary>
        /// 绕着某向量旋转指定角度得到新向量
        /// </summary>
        /// <param name="rotationAxis">旋转轴向量</param>
        /// <param name="angleInRadians">旋转角度</param>
        /// <returns></returns>
        public XYZ AngleByAxis(XYZ rotationAxis, double angleInRadians)
        {

            XYZ origXYZ = new XYZ(X, Y, Z);
            // 计算旋转矩阵
            double c = Math.Cos(angleInRadians);
            double s = Math.Sin(angleInRadians);
            double t = 1 - c;

            double x = origXYZ.X * (rotationAxis.X * rotationAxis.X * t + c) +
                       origXYZ.Y * (rotationAxis.X * rotationAxis.Y * t - rotationAxis.Z * s) +
                       origXYZ.Z * (rotationAxis.X * rotationAxis.Z * t + rotationAxis.Y * s);

            double y = origXYZ.X * (rotationAxis.Y * rotationAxis.X * t + rotationAxis.Z * s) +
                       origXYZ.Y * (rotationAxis.Y * rotationAxis.Y * t + c) +
                       origXYZ.Z * (rotationAxis.Y * rotationAxis.Z * t - rotationAxis.X * s);

            double z = origXYZ.X * (rotationAxis.Z * rotationAxis.X * t - rotationAxis.Y * s) +
                       origXYZ.Y * (rotationAxis.Z * rotationAxis.Y * t + rotationAxis.X * s) +
                       origXYZ.Z * (rotationAxis.Z * rotationAxis.Z * t + c);

            return new XYZ(x, y, z);
        }

        /// <summary>
        /// 返回两个向量的叉积
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public XYZ CrossProduct(XYZ source)
        {
            XYZ origXYZ = new XYZ(X, Y, Z);
            double crossX = origXYZ.Y * source.Z - origXYZ.Z * source.Y;
            double crossY = origXYZ.Z * source.X - origXYZ.X * source.Z;
            double crossZ = origXYZ.X * source.Y - origXYZ.Y * source.X;

            return new XYZ(crossX, crossY, crossZ);
        }

        /// <summary>
        /// 返回向量点积
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public double DotProduct(XYZ source)
        {
            return this.X * source.X + this.Y * source.Y + this.Z * source.Z;
        }

        /// <summary>
        /// 返回向量的长度
        /// </summary>
        /// <returns></returns>
        public double Length()
        {
            return Math.Sqrt(Math.Pow(X, 2) + Math.Pow(Y, 2) + Math.Pow(Z, 2));
        }

        /// <summary>
        /// 保留小数四舍五入
        /// </summary>
        /// <param name="count">保留小数位数</param>
        /// <returns></returns>
        public XYZ RoundDecimal(int count = 2)
        {
            double value = Math.Pow(10, count);
            return new XYZ(Math.Round(X * value) / value, Math.Round(Y * value) / value, Math.Round(Z * value) / value);
        }

        /// <summary>
        /// 当前向量减去目标向量得到新向量
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public XYZ Subtract(XYZ source)
        {
            return new XYZ(X - source.X, Y - source.Y, Z - source.Z);
        }

        /// <summary>
        /// 判断两个向量是否平行
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public bool IsParallel(XYZ source)
        {

            if (new XYZ(X, Y, Z).Normalize().Subtract(source.Normalize()).Length() < 1e-9)
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        /// <summary>
        /// 将点坐标转化为字符串
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return X.ToString() + " " + Y.ToString() + " " + Z.ToString();
        }

        public XYZ(double x, double y, double z)
        {
            X = x;
            Y = y;
            Z = z;
        }

        // 将自定义 XYZ 转换为 Revit API XYZ
        public Autodesk.Revit.DB.XYZ ToRevitXYZ()
        {
            return new Autodesk.Revit.DB.XYZ(X, Y, Z);
        }
    }
}
